{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.855713Z",
     "start_time": "2025-01-17T13:46:54.849423Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 46
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.898686Z",
     "start_time": "2025-01-17T13:46:54.886294Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 47
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.905805Z",
     "start_time": "2025-01-17T13:46:54.900688Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 48
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.915004Z",
     "start_time": "2025-01-17T13:46:54.906810Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 49
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.923009Z",
     "start_time": "2025-01-17T13:46:54.916008Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-4 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-4 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-4 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-4 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-4 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-4 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-4 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-4 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-4 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-4 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-4 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-4 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-4 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-4 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-4 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-4 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-4 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-4 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-4\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-4\" type=\"checkbox\" checked><label for=\"sk-estimator-id-4\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 50
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.937071Z",
     "start_time": "2025-01-17T13:46:54.930583Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 51
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.951191Z",
     "start_time": "2025-01-17T13:46:54.945568Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 52
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:54.964383Z",
     "start_time": "2025-01-17T13:46:54.960743Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 8  #过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 53
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:55.000353Z",
     "start_time": "2025-01-17T13:46:54.995412Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30), #30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30), #30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # pytorch 需要自行计算输出输出维度   wide的输入直接拿过来，是8；deep的输入是8，最后输出是30，所以最后总输出是wide的加上deep的，即为38\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        concat = torch.cat([x, deep_output], dim=1)  # torch.cat:将多个张量沿着指定的维度（dim）连接起来，生成一个新的张量。\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 54
  },
  {
   "cell_type": "code",
   "source": [
    "# train_ds[0][0]\n",
    "#验证模型是否正确\n",
    "input=train_ds[0][0].reshape(1, -1)\n",
    "print(input.shape)\n",
    "model=WideDeep()\n",
    "out=model(input)\n",
    "out.shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:55.015479Z",
     "start_time": "2025-01-17T13:46:55.008904Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 8])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 1])"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 55
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:55.021793Z",
     "start_time": "2025-01-17T13:46:55.016484Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 56
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:46:55.031332Z",
     "start_time": "2025-01-17T13:46:55.026815Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 57
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:47:16.480148Z",
     "start_time": "2025-01-17T13:46:55.059811Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 10\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "1cdc7733177641838bb21257b735971f"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 58
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:47:16.593072Z",
     "start_time": "2025-01-17T13:47:16.481153Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 59
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T13:47:16.718908Z",
     "start_time": "2025-01-17T13:47:16.593616Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4249\n"
     ]
    }
   ],
   "execution_count": 60
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
